package cn.edu.neu.simpleppt;

import javafx.beans.binding.DoubleExpression;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.scene.transform.Rotate;

import java.util.Vector;

/**
 * 用来改变图形大小，拖动图形的控件，以及对应的事件处理
 * 在触发 to_bound 的 click 事件时，调用 getControl 并添加到画板上
 */
public class ControlPoints {
    // 要控制的元素
    private final Node to_bind;

    // 显示在界面上的控制框
    private Group group;

    // 显示边界的矩形
    private Rectangle nodeBound;

    // 用来旋转图形的控制点
    private Circle rotateControl;

    // 控制点，拖动可以改变图形大小
    private final Circle[] controls = new Circle[8];
    private static final int TopLeft = 0;
    private static final int Top = 1;
    private static final int TopRight = 2;
    private static final int Left = 3;
    private static final int Right = 4;
    private static final int BottomLeft = 5;
    private static final int Bottom = 6;
    private static final int BottomRight = 7;

    // 记录拖拽时的参考点
    Point2D lastRotateVec;
    Point2D lastMousePoint;

    // 记录元素被点击时的初始状态
    double lastTransformX;
    double lastTransformY;
    double lastHeight;
    double lastWidth;
    double lastCenterY;
    double lastCenterX;
    double lastLayoutX;
    double lastLayoutY;

    // 任意线内部存储的数据
    Vector<Double> lastPoints = new Vector<>();

    /**
     * 构造函数
     * @param to_bind 要控制的元素
     */
    public ControlPoints(Node to_bind) {
        this.to_bind = to_bind;
        initGroup();
        initEvent();
    }

    private Circle createCircle() {
        Circle circle = new Circle(6);
        circle.setStrokeWidth(2);
        circle.setStroke(Color.BLACK);
        circle.setFill(Color.WHITE);
        return circle;
    }

    /**
     * 在图形位置、大小发生变化时、更新边界框的位置和大小
     */
    private void updateRectangle(Rectangle rectangle) {
        Bounds node_bounds = to_bind.getBoundsInLocal();
        rectangle.setLayoutX(node_bounds.getMinX() + to_bind.getTranslateX());
        rectangle.setLayoutY(node_bounds.getMinY() + to_bind.getTranslateY());
        rectangle.setWidth(node_bounds.getWidth() * to_bind.getScaleX());
        rectangle.setHeight(node_bounds.getHeight() * to_bind.getScaleY());
    }

    public void updateBounds() {
        updateRectangle(nodeBound);
    }

    /**
     * 显示在界面上，显示元素边界的矩形
     * @return 矩形
     */
    private Rectangle createRectangle() {
        Rectangle rectangle = new Rectangle();
        updateRectangle(rectangle);
        rectangle.setStrokeDashOffset(2);
        rectangle.setStrokeWidth(2);
        rectangle.setStroke(Color.GRAY);
        rectangle.setFill(Color.TRANSPARENT);
        return rectangle;
    }

    /**
     * 控制框的所有组成部分，由用来控制元素大小、旋转、元素位置的圆圈、表示边界的矩形组成
     */
    private void initGroup() {
        group = new Group();

        nodeBound = createRectangle();
        for (int i = 0; i < 8; i++) {
            controls[i] = createCircle();
        }

        rotateControl = createCircle();
        rotateControl.setFill(Color.GREEN);
        Line line = new Line();
        line.setStrokeWidth(3);
        line.startXProperty().bind(nodeBound.layoutXProperty().add(nodeBound.widthProperty().divide(2)));
        line.endXProperty().bind(nodeBound.layoutXProperty().add(nodeBound.widthProperty().divide(2)));
        line.startYProperty().bind(nodeBound.layoutYProperty());
        line.endYProperty().bind(nodeBound.layoutYProperty().subtract(30));
        rotateControl.centerXProperty().bind(nodeBound.layoutXProperty().add(nodeBound.widthProperty().divide(2)));
        rotateControl.centerYProperty().bind(nodeBound.layoutYProperty().subtract(30));

        group.layoutXProperty().bind(to_bind.layoutXProperty());
        group.layoutYProperty().bind(to_bind.layoutYProperty());
        group.getChildren().addAll(nodeBound, line, rotateControl);
        group.getChildren().addAll(controls);
        // 控制组件的旋转
        Rotate rotate = new Rotate();
        rotate.angleProperty().bind(to_bind.rotateProperty());
        rotate.pivotXProperty().bind(nodeBound.layoutXProperty().add(nodeBound.widthProperty().divide(2)));
        rotate.pivotYProperty().bind(nodeBound.layoutYProperty().add(nodeBound.heightProperty().divide(2)));
        group.getTransforms().add(rotate);

        DoubleExpression[] xProperties = {
                nodeBound.layoutXProperty(),
                nodeBound.layoutXProperty().add(nodeBound.widthProperty().divide(2)),
                nodeBound.layoutXProperty().add(nodeBound.widthProperty()),
                nodeBound.layoutXProperty(),
                nodeBound.layoutXProperty().add(nodeBound.widthProperty()),
                nodeBound.layoutXProperty(),
                nodeBound.layoutXProperty().add(nodeBound.widthProperty().divide(2)),
                nodeBound.layoutXProperty().add(nodeBound.widthProperty()),
        };
        DoubleExpression[] yProperties = {
                nodeBound.layoutYProperty(),
                nodeBound.layoutYProperty(),
                nodeBound.layoutYProperty(),
                nodeBound.layoutYProperty().add(nodeBound.heightProperty().divide(2)),
                nodeBound.layoutYProperty().add(nodeBound.heightProperty().divide(2)),
                nodeBound.layoutYProperty().add(nodeBound.heightProperty()),
                nodeBound.layoutYProperty().add(nodeBound.heightProperty()),
                nodeBound.layoutYProperty().add(nodeBound.heightProperty())
        };
        Cursor[] controlCursors = {
                Cursor.NW_RESIZE,
                Cursor.N_RESIZE,
                Cursor.NE_RESIZE,
                Cursor.W_RESIZE,
                Cursor.E_RESIZE,
                Cursor.SW_RESIZE,
                Cursor.S_RESIZE,
                Cursor.SE_RESIZE
        };
        for (int i = 0; i < controls.length; i++) {
            controls[i].centerXProperty().bind(xProperties[i]);
            controls[i].centerYProperty().bind(yProperties[i]);
            controls[i].setCursor(controlCursors[i]);
        }

        rotateControl.setCursor(Cursor.MOVE);
        nodeBound.setCursor(Cursor.MOVE);
    }

    /**
     * 获取当前鼠标移动的旋转方向
     * @return 1, 顺时针；-1, 逆时针
     */
    private int getRotateDirection(Point2D moveVec, Point2D thisRotateVec) {
        if (thisRotateVec.getX() > 0) {
            if (thisRotateVec.getY() < 0) {
                return (moveVec.getX() > 0 || moveVec.getY() > 0) ? 1 : -1;
            } else {
                return (moveVec.getX() < 0 || moveVec.getY() > 0) ? 1: -1;
            }
        } else {
            if (thisRotateVec.getY() > 0) {
                return (moveVec.getY() < 0 || moveVec.getX() < 0) ? 1 : -1;
            } else {
                return (moveVec.getY() < 0 || moveVec.getX() > 0) ? 1 : -1;
            }
        }
    }

    private void onTopDragging(MouseEvent e) {
        Point2D delta = new Point2D(e.getScreenX(), e.getScreenY()).subtract(lastMousePoint);

        to_bind.setTranslateY(lastTransformY + delta.getY());

        if (to_bind instanceof Ellipse) {
            Ellipse ellipse = (Ellipse) to_bind;
            ellipse.setRadiusY(lastCenterY - delta.getY() / 2);
            ellipse.setCenterY(lastCenterY - delta.getY() / 2);
        }
        if (to_bind instanceof Rectangle) {
            ((Rectangle) to_bind).setHeight(lastHeight - delta.getY());
        }
        if (to_bind instanceof Line) {
            ((Line) to_bind).setEndY(lastHeight - delta.getY());
        }
        if (to_bind instanceof Label) {
            ((Label) to_bind).setPrefHeight(lastHeight - delta.getY());
        }
        if (to_bind instanceof Polyline) {
            Polyline polyline = (Polyline) to_bind;
            ObservableList<Double> points = polyline.getPoints();
            for (int i = 1; i < points.size(); i += 2 ) {
                points.set(i, lastPoints.get(i) * (lastHeight - delta.getY()));
            }
        }
        updateRectangle(nodeBound);
    }

    private void onBottomDragging(MouseEvent e) {
        Point2D delta = new Point2D(e.getScreenX(), e.getScreenY()).subtract(lastMousePoint);
        if (to_bind instanceof Ellipse) {
            Ellipse ellipse = (Ellipse) to_bind;
            ellipse.setRadiusY(lastCenterY + delta.getY() / 2);
            ellipse.setCenterY(lastCenterY + delta.getY() / 2);
        }
        if (to_bind instanceof Rectangle) {
            ((Rectangle) to_bind).setHeight(lastHeight +delta.getY());
        }
        if (to_bind instanceof Line) {
            ((Line) to_bind).setEndY(lastHeight +delta.getY());
        }
        if (to_bind instanceof Label) {
            ((Label) to_bind).setPrefHeight(lastHeight + delta.getY());
        }
        if (to_bind instanceof Polyline) {
            Polyline polyline = (Polyline) to_bind;
            ObservableList<Double> points = polyline.getPoints();
            for (int i = 1; i < points.size(); i += 2 ) {
                points.set(i, lastPoints.get(i) * (lastHeight + delta.getY()));
            }
        }
        updateRectangle(nodeBound);
    }

    private void onLeftDragging(MouseEvent e) {
        Point2D delta = new Point2D(e.getScreenX(), e.getScreenY()).subtract(lastMousePoint);

        to_bind.setTranslateX(lastTransformX + delta.getX());

        if (to_bind instanceof Ellipse) {
            Ellipse ellipse = (Ellipse) to_bind;
            ellipse.setRadiusX(lastCenterX - delta.getX() / 2);
            ellipse.setCenterX(lastCenterX - delta.getX() / 2);
        }
        if (to_bind instanceof Rectangle) {
            ((Rectangle) to_bind).setWidth(lastWidth - delta.getX());
        }
        if (to_bind instanceof Line) {
            ((Line) to_bind).setEndX(lastWidth - delta.getX());
        }
        if (to_bind instanceof Label) {
            ((Label) to_bind).setPrefWidth(lastWidth - delta.getX());
        }
        if (to_bind instanceof Polyline) {
            Polyline polyline = (Polyline) to_bind;
            ObservableList<Double> points = polyline.getPoints();
            for (int i = 0; i < points.size(); i += 2 ) {
                points.set(i, lastPoints.get(i) * (lastWidth - delta.getX()));
            }
        }
        updateRectangle(nodeBound);
    }

    private void onRightDragging(MouseEvent e) {
        Point2D delta = new Point2D(e.getScreenX(), e.getScreenY()).subtract(lastMousePoint);

        if (to_bind instanceof Ellipse) {
            Ellipse ellipse = (Ellipse) to_bind;
            ellipse.setRadiusX(lastCenterX + delta.getX() / 2);
            ellipse.setCenterX(lastCenterX + delta.getX() / 2);
        }
        if (to_bind instanceof Rectangle) {
            ((Rectangle) to_bind).setWidth(lastWidth + delta.getX());
        }
        if (to_bind instanceof Line) {
            ((Line) to_bind).setEndX(lastWidth + delta.getX());
        }
        if (to_bind instanceof Label) {
            ((Label) to_bind).setPrefWidth(lastWidth + delta.getX());
        }
        if (to_bind instanceof Polyline) {
            Polyline polyline = (Polyline) to_bind;
            ObservableList<Double> points = polyline.getPoints();
            for (int i = 0; i < points.size(); i += 2 ) {
                points.set(i, lastPoints.get(i) * (lastWidth + delta.getX()));
            }
        }
        updateRectangle(nodeBound);
    }

    // 设置控制框的事件
    private void initEvent() {
        // 对图形进行旋转操作
        rotateControl.addEventFilter(MouseDragEvent.MOUSE_PRESSED, e -> {
            Bounds node_in_screen = nodeBound.localToScreen(to_bind.getBoundsInLocal());
            lastRotateVec = new Point2D(e.getScreenX(), e.getScreenY()).subtract(node_in_screen.getCenterX(), node_in_screen.getCenterY());
        });
        rotateControl.addEventFilter(MouseDragEvent.MOUSE_DRAGGED, e-> {
            Bounds node_in_screen = nodeBound.localToScreen(to_bind.getBoundsInLocal());
            Point2D thisRotateVec = new Point2D(e.getScreenX(), e.getScreenY()).subtract(node_in_screen.getCenterX(), node_in_screen.getCenterY());

            // 判断旋转方向
            int direction = getRotateDirection(thisRotateVec.subtract(lastRotateVec), thisRotateVec);
            double angle = lastRotateVec.angle(thisRotateVec);
            to_bind.setRotate(to_bind.getRotate() + direction * angle);
            lastRotateVec = thisRotateVec;
        });

        EventHandler<MouseEvent> on_start_drag = e->{
            Bounds bounds = to_bind.getBoundsInLocal();
            lastMousePoint = new Point2D(e.getScreenX(), e.getScreenY());
            if (to_bind instanceof Ellipse) {
                lastCenterY = ((Ellipse) to_bind).getRadiusY();
                lastCenterX = ((Ellipse) to_bind).getRadiusX();
            }
            lastTransformY = to_bind.getTranslateY();
            lastTransformX = to_bind.getTranslateX();
            lastHeight = bounds.getHeight();
            lastWidth = bounds.getWidth();
            lastLayoutX = to_bind.getLayoutX();
            lastLayoutY = to_bind.getLayoutY();

            if (to_bind instanceof Polyline) {
                Polyline polyline = (Polyline) to_bind;
                lastPoints.clear();

                // 计算每个点的百分比位置
                ObservableList<Double> points = polyline.getPoints();
                // i = 0, 2, 4, 6 ... x
                // i = 1, 3, 5, 7 ... y
                for (int i = 0; i < points.size(); i++) {
                    if (i % 2 == 0) lastPoints.add((points.get(i)  - bounds.getMinX()) / bounds.getWidth());
                    else lastPoints.add((points.get(i)  - bounds.getMinY()) / bounds.getHeight());
                }
            }
        };

        controls[Top].addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        controls[Top].addEventFilter(MouseDragEvent.MOUSE_DRAGGED, this::onTopDragging);

        controls[Bottom].addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        controls[Bottom].addEventFilter(MouseDragEvent.MOUSE_DRAGGED, this::onBottomDragging);

        controls[Left].addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        controls[Left].addEventFilter(MouseDragEvent.MOUSE_DRAGGED, this::onLeftDragging);

        controls[Right].addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        controls[Right].addEventFilter(MouseDragEvent.MOUSE_DRAGGED, this::onRightDragging);

        controls[TopLeft].addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        controls[TopLeft].addEventFilter(MouseDragEvent.MOUSE_DRAGGED, e-> {
            onTopDragging(e);
            onLeftDragging(e);
        });

        controls[TopRight].addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        controls[TopRight].addEventFilter(MouseDragEvent.MOUSE_DRAGGED, e-> {
            onTopDragging(e);
            onRightDragging(e);
        });

        controls[BottomLeft].addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        controls[BottomLeft].addEventFilter(MouseDragEvent.MOUSE_DRAGGED, e-> {
            onBottomDragging(e);
            onLeftDragging(e);
        });

        controls[BottomRight].addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        controls[BottomRight].addEventFilter(MouseDragEvent.MOUSE_DRAGGED, e-> {
            onBottomDragging(e);
            onRightDragging(e);
        });

        // 拖动 shape 移动位置
        nodeBound.addEventFilter(MouseDragEvent.MOUSE_PRESSED, on_start_drag);
        nodeBound.addEventFilter(MouseDragEvent.MOUSE_DRAGGED, e-> {
            Point2D delta = (new Point2D(e.getScreenX(), e.getScreenY())).subtract(lastMousePoint);
            to_bind.setLayoutX(lastLayoutX + delta.getX());
            to_bind.setLayoutY(lastLayoutY + delta.getY());
        });
    }

    public Group getControl() {
        return group;
    }
}
